#include "stdstratego.h"
#include "stdsc.h"

#include "ilpstrategies.h"
#include "linebyline.h"

#define PREPROLOGUE 0
#define PROLOGUE    1
#define REPEAT      2
#define EPILOGUE    3

typedef struct skelcode_s
{
  code body;
  code split[];
} skelcode_t;

stratego code metasplit(code body)
{
  strategies
    main =
        <table-put>("globals", "prologue", Compound([], []))
      ; <table-put>("globals", "repeat", Compound([], []))
      ; <table-put>("globals", "epilogue", Compound([], []))
      ; <topdown(try(MetaSplit <+ AddPromote))>(body)
      ; ![<id>, <table-get>("globals", "prologue")
              , <table-get>("globals", "repeat")
              , <table-get>("globals", "epilogue")
         ]
      ; map(\a -> AssignInit(CodeConst(a))\); \a -> ArrayInit(a)\; id

  rules
    MetaSplit:
      Stat(FunCall(Id(i), [CodeConst(c)])) -> Stat(EmptyExp)
      where ( <elem>(i, ["prologue", "repeat", "epilogue"])
              ; <table-put>("globals", i, c)
            )

    AddPromote:
      Declaration2(t@TypeSpec(a, b, c),i) ->
      Declaration2(DeclSpec([Promote], t), i)

    AddPromote:
      Declaration2(DeclSpec(d, t), i) ->
      Declaration2(DeclSpec([Promote|d], t), i)
}

static int instanceBufferGetStride(int ii, int aa)
{
  if (instance[ii].argcode[aa].connector.instance == -1)
    return scuBufferGetStride(args[instance[ii].argcode[aa].connector.argument].buffer);
  else
    error("Interactions with intermediate buffers not allowed");
}

static int instanceBufferGetLines(int ii, int aa)
{
  if (instance[ii].argcode[aa].connector.instance == -1)
    return scuBufferGetLines(args[instance[ii].argcode[aa].connector.argument].buffer);
  else
    error("Interactions with intermediate buffers not allowed");
}

static int instanceBufferRemoveReference(int ii, int aa)
{
  if (instance[ii].argcode[aa].connector.instance == -1)
    return scuBufferRemoveReference(args[instance[ii].argcode[aa].connector.argument].buffer);
  else
    return 0;
}

static int instanceBufferFinalize(int ii, int aa)
{
  if (instance[ii].argcode[aa].connector.instance == -1)
    return scuBufferFinalize(args[instance[ii].argcode[aa].connector.argument].buffer);
  else
    return 0;
}

static int instanceBufferPeek(int ii, int aa, void **data, int size, int options)
{
  if (instance[ii].argcode[aa].connector.instance == -1)
    return scuBufferPeek(args[instance[ii].argcode[aa].connector.argument].buffer,
                         args[instance[ii].argcode[aa].connector.argument].reader,
                        data, size, options);
  else
    error("Interactions with intermediate buffers not allowed");
}

static void instanceBufferReleasePeeked(int ii, int aa, int size)
{
  if (instance[ii].argcode[aa].connector.instance == -1)
    scuBufferReleasePeeked(args[instance[ii].argcode[aa].connector.argument].buffer,
                           args[instance[ii].argcode[aa].connector.argument].reader,
                           size);
  else
    error("Interactions with intermediate buffers not allowed");
}

static int instanceBufferAllocate(int ii, int aa, void **data, int size, int options)
{
  if (instance[ii].argcode[aa].connector.instance == -1)
    return scuBufferAllocate(args[instance[ii].argcode[aa].connector.argument].buffer,
                             data, size, options);
  else
    error("Interactions with intermediate buffers not allowed");
}

static void instanceBufferReleaseAllocated(int ii, int aa, int size)
{
  if (instance[ii].argcode[aa].connector.instance == -1)
    scuBufferReleaseAllocated(args[instance[ii].argcode[aa].connector.argument].buffer,
                              size);
  else
    error("Interactions with intermediate buffers not allowed");
}

static code rewriteBuffers(int ii, code body)
{
  body = rewrite(`BufferGetStride(PH1)`, `instanceBufferGetStride($ii, PH1)`, body);
  body = rewrite(`BufferGetLines(PH1)`, `instanceBufferGetLines($ii, PH1)`, body);
  body = rewrite(`BufferRemoveReference(PH1)`, `instanceBufferRemoveReference($ii, PH1)`, body);
  body = rewrite(`BufferFinalize(PH1)`, `instanceBufferFinalize($ii, PH1)`, body);
  body = rewrite(`BufferPeek(PH1, PH2, PH3, PH4)`, `instanceBufferPeek($ii, PH1, PH2, PH3, PH4)`, body);
  body = rewrite(`BufferReleasePeeked(PH1, PH2)`, `instanceBufferReleasePeeked($ii, PH1, PH2)`, body);
  body = rewrite(`BufferAllocate(PH1, PH2, PH3, PH4)`, `instanceBufferAllocate($ii, PH1, PH2, PH3, PH4)`, body);
  body = rewrite(`BufferReleaseAllocated(PH1, PH2)`, `instanceBufferReleaseAllocated($ii, PH1, PH2)`, body);

  return body;
}

void pixelbypixel(instance_t *instance, int instances)
{
  skelcode_t skelcode[];
  int ii, jj, kk, fii=-1, fia=-1;
  int pbp_hpixels, pbp_ii, stride;

  for (ii=0; ii < instances; ii++)
  {
    /* Make sure function calls in registered codes are expanded */
    instance[ii].body = rewrite(`prologue(PH1)`, `prologue(reduce(PH1, 1))`, instance[ii].body);
    instance[ii].body = rewrite(`repeat(PH1)`, `repeat(reduceit(PH1, 1))`, instance[ii].body);
    instance[ii].body = rewrite(`epilogue(PH1)`, `epilogue(reduce(PH1, 1))`, instance[ii].body);

    /* Add skeleton functions to symbol table */
    @addpromote(instance[ii].body);

    /* Call skeleton, keeping expanded code */
    skelcode[ii].body = reduce(`($@instance[ii].skeleton)(instance[ii].argcode, instance[ii].arguments, instance[ii].kernel)`);

    /* Extract registered codes, and make variables unique */
    skelcode[ii].split = @metasplit(skelcode[ii].body);
    skelcode[ii].split = @scramblevariables(skelcode[ii].split, makeid(`i`, ii));

    /* Rewrite buffers to add instance information */
    for (jj=0; jj < 4; jj++)
      if (jj != REPEAT)
        skelcode[ii].split[jj] = rewriteBuffers(ii, skelcode[ii].split[jj]);

    /* Rename arguments to match scrambled variables */
    for (jj=0; jj < instance[ii].arguments; jj++)
    {
      instance[ii].argcode[jj].id = makeid(makeid(makeid(`i`, ii), instance[ii].argcode[jj].id), `0`);

      if (instance[ii].argcode[jj].connector.instance == -1 &&
          instance[ii].argcode[jj].argtype == 0 && fia == -1)
      {
        fii = ii;
        fia = jj;
      }
    }

    for (jj=0; jj < instance[ii].arguments; jj++)
    {
      int ci = instance[ii].argcode[jj].connector.instance;
      int ca = instance[ii].argcode[jj].connector.argument;

      if (ci == -1)
      {
        if (instance[ii].argcode[jj].argtype == ARG_STREAM_IN ||
            instance[ii].argcode[jj].argtype == ARG_STREAM_OUT)
        {
          /* Create new variable to interact with the outside world */
          code newid = makeid(`oa`, ca);

          @declare(addrpointer(instance[ii].argcode[jj].datatype), newid);

          for (kk=1; kk < 4; kk++)
            skelcode[ii].split[kk] = replace(instance[ii].argcode[jj].id,
                                             `($@newid)[pbp_ii]`, skelcode[ii].split[kk]);
      
          instance[ii].argcode[jj].id = newid;
        }

      }
      else if (instance[ii].argcode[jj].argtype == ARG_STREAM_IN)
      {
        /* Connect by referencing output variable */
        for (kk=1; kk < 4; kk++)
          skelcode[ii].split[kk] = replace(instance[ii].argcode[jj].id,
                                           instance[ci].argcode[ca].id, skelcode[ii].split[kk]);

        instance[ii].argcode[jj].id = instance[ci].argcode[ca].id;
      }
    }
  }
  
  stride = scuBufferGetStride(args[instance[fii].argcode[fia].connector.argument].buffer);
  pbp_hpixels = stride;

  /* Preprologue (initialize variables) */
  for (ii=0; ii < instances; ii++)
    @skelcode[ii].split[0];

  propEnvReset();
  propEnv("operation", operation);

  for (ii=0; ii < instances; ii++)
    for (jj=0; jj < instance[ii].arguments; jj++)
    {
      int ci = instance[ii].argcode[jj].connector.instance;
      int ca = instance[ii].argcode[jj].connector.argument;

      if (ci == -1)
      {
        propEnv("argument", @idtostring(makeid(`id`, ca)));
          propSeti("id", ca);
          propEnv("target", target);
            propSeti("buffer", instance[ii].argcode[jj].state.buffer);
            propSeti("state", instance[ii].argcode[jj].state.state);
            propSeti("delay", instance[ii].argcode[jj].state.delay);
            propSeti("elementsize", sizeof(instance[ii].argcode[jj].datatype));

            if (instance[ii].argcode[jj].argtype == ARG_STREAM_IN ||
                instance[ii].argcode[jj].argtype == ARG_STREAM_OUT ||
                instance[ii].argcode[jj].argtype == ARG_LOOKUP)
              propSeti("chunksize", 1024);
            else
              propSeti("chunksize", 1);

            
            if (instance[ii].argcode[jj].argtype == ARG_SCALAR_IN ||
                instance[ii].argcode[jj].argtype == ARG_SCALAR_OUT ||
                instance[ii].argcode[jj].argtype == ARG_LOOKUP)
              propSets("timedist", "bulk");
            else
              propSets("timedist", "regular");
          propEnvBack();
        propEnvBack();
      }
    }

  /* Prologue */ 
  for (ii=0; ii < instances; ii++)
    @skelcode[ii].split[1];

  /* Actual processing */
  while (pbp_hpixels = scuBufferPeek(args[instance[fii].argcode[fia].connector.argument].buffer,
                                     args[instance[fii].argcode[fia].connector.argument].reader,
                       (void**)&(@instance[fii].argcode[fia].id), stride*sizeof(instance[fii].argcode[fia].datatype), 1))
  {
    pbp_hpixels = pbp_hpixels / sizeof(instance[fii].argcode[fia].datatype);

    for (ii=0; ii < instances; ii++)
      for (jj=0; jj < instance[ii].arguments; jj++)
      {
        argcode_t *ac = &instance[ii].argcode[jj];

        if ((ii != fii || jj != fia) && (*ac).argtype == 0 && (*ac).connector.instance == -1)
          scuBufferPeek(args[(*ac).connector.argument].buffer,
                        args[(*ac).connector.argument].reader,
                        (void**)&(@instance[ii].argcode[jj].id),
                        pbp_hpixels*sizeof(instance[ii].argcode[jj].datatype), 1);
        else if (instance[ii].argcode[jj].argtype == 1 && (*ac).connector.instance == -1)
          scuBufferAllocate(args[(*ac).connector.argument].buffer, (void**)&(@instance[ii].argcode[jj].id), pbp_hpixels*sizeof(instance[ii].argcode[jj].datatype),
                         1);
      }

    for (pbp_ii=0; pbp_ii < pbp_hpixels; pbp_ii++)
      for (ii=0; ii < instances; ii++)
        @skelcode[ii].split[2];

    for (ii=0; ii < instances; ii++)
      for (jj=0; jj < instance[ii].arguments; jj++)
      {
        argcode_t *ac = &instance[ii].argcode[jj];

        if (instance[ii].argcode[jj].argtype == 0 && (*ac).connector.instance == -1)
          scuBufferReleasePeeked(args[(*ac).connector.argument].buffer, args[(*ac).connector.argument].reader, pbp_hpixels*sizeof(instance[ii].argcode[jj].datatype));
        else if (instance[ii].argcode[jj].argtype == 1 && (*ac).connector.instance == -1)
          scuBufferReleaseAllocated(args[(*ac).connector.argument].buffer, pbp_hpixels*sizeof(instance[ii].argcode[jj].datatype));
      }
  }

  /* Epilogue (release buffers) */
  for (ii=0; ii < instances; ii++)
    @skelcode[ii].split[3];
}
